package net.towee.client.widgets.upload.impl;

import net.towee.client.widgets.upload.UploadError;
import net.towee.client.widgets.upload.UploadEvent;
import net.towee.client.widgets.upload.UploadForm;
import net.towee.client.widgets.upload.UploadHandler;
import net.towee.client.widgets.upload.UploadItem;
import net.towee.client.widgets.upload.UploadStartHandler;
import net.towee.client.widgets.upload.fileapi.File;
import net.towee.client.widgets.upload.fileapi.FilesList;

import com.google.common.base.Preconditions;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.DropEvent;
import com.google.gwt.event.dom.client.DropHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.Widget;

/**
 * Implementation of UploadForm working with XHR2 (HTML5)
 * 
 * @author c58
 */
public class UploadFormXHR extends AbstractUploadForm implements UploadForm, ChangeHandler {

	/**
	 * Extended FileUpload input area. It can work with multiple file selection.
	 * 
	 * @author viiv.c58@gmail.com (Artem Artem'ev)
	 */
	public class MultipleFileUpload extends FileUpload {

		public MultipleFileUpload() {
			DOM.setElementAttribute(getElement(), "multiple", "multiple");
		}

		/**
		 * @return List of files
		 */
		public FilesList getFilesList() {
			return new FilesList(getNativeFilesList((InputElement) getElement().cast()));
		}

		private native JavaScriptObject getNativeFilesList(InputElement test) /*-{
			return test.files;
		}-*/;
	}

	/**
	 * Implementation of upload item for working with XHR controller
	 * 
	 * @author c58
	 */
	private class UploadItemImpl extends AbstractUploadItem implements XHRUploadHandler {
		private final XHRUploadController controller;
		private long logLoadedBytes = 0;

		public UploadItemImpl(File fileItem) {
			this.controller = new XHRUploadController(fileItem, this);
			this.fileName = fileItem.getName();
			this.fileSize = fileItem.getSize();
		}

		@Override
		public void start() {
			Preconditions.checkNotNull(handler, "you must set handler first");

			if (fileSize > maxFileSize)
				handler.onErrorUploading(UploadError.FILE_SIZE_LIMIT);
			else {
				globalTotalSize += fileSize;

				if (uploadingPoolSize == MAX_CONCURENT_UPLOADING) {
					handler.onUploadItemQueued();
					uploadQueue.add(this);
					state = UploadState.QUEUED;
				} else {
					uploadingPoolSize++;
					state = UploadState.STARTED;
					handler.onUploadingStarted(true);
					controller.send();
				}
			}
		}

		@Override
		public void cancel() {
			uploadItems.remove(this);
			controller.abort();

			globalTotalSize -= fileSize;
		}

		@Override
		public void onLoaded(String response) {
			if (response.length() <= 2) {
				state = UploadState.ERROR;
				handler.onErrorUploading(UploadError.fromString(response));
			} else {
				state = UploadState.COMPLITED;
				handler.onCompleteUploading(response);
				UploadFormXHR.this.fireEvent(new UploadEvent(response));
			}

			freeQueue();
		}

		@Override
		public void onProgress(int loaded, int total) {
			globalLoadedSize += (logLoadedBytes == 0) ? loaded : loaded - logLoadedBytes;
			logLoadedBytes = loaded;

			handler.onProgressUpdated((int) (((double) loaded / (double) total) * 100.), loaded, total);
		}

		@Override
		public void onError() {
			state = UploadState.ERROR;

			globalLoadedSize -= logLoadedBytes;
			globalTotalSize -= fileSize;

			handler.onErrorUploading(UploadError.UNKNOWN);
			freeQueue();
		}

		@Override
		public void onAbort() {
			state = UploadState.CANCELED;

			globalLoadedSize -= logLoadedBytes;
			globalTotalSize -= fileSize;

			handler.onCancelUploading();

			freeQueue();
		}

		@Override
		public void onDataURLReed(String dataUrl) {
			handler.onDataURLReed(dataUrl);
		}
	}

	// List of handlers
	private final MultipleFileUpload uploadForm;

	public UploadFormXHR() {
		this.uploadForm = new MultipleFileUpload();

		createInputForm();
	}

	/**
	 * Create input form. It create drop event handler if supported or add file
	 * selecting form.
	 */
	private void createInputForm() {

		// Add drop handler
		addDomHandler(new DropHandler() {
			@Override
			public void onDrop(DropEvent event) {
				event.preventDefault();
				processFilesList(new FilesList(getNativeFilesList(event.getNativeEvent())));
			}

			private native JavaScriptObject getNativeFilesList(JavaScriptObject event) /*-{
		return event.dataTransfer.files;
	}-*/;
		}, DropEvent.getType());

		// Add normal upload form
		uploadForm.addChangeHandler(this);
		add(uploadForm);
	}

	@Override
	public void onChange(ChangeEvent event) {
		processFilesList(uploadForm.getFilesList());
	}

	/**
	 * Start processing selected files.
	 * 
	 * @param list
	 */
	private void processFilesList(FilesList list) {
		for (int i = 0; i < list.getLength(); i++) {
			if (uploadItems.size() == filesLimit) {
				for (UploadStartHandler h : handlers)
					h.onOutOfLimit();

				break;
			}

			for (UploadStartHandler h : handlers) {
				UploadItem item = new UploadItemImpl(list.getFile(i));
				uploadItems.add(item);

				h.onUploadingStarted(item);
			}
		}
	}

	@Override
	public HandlerRegistration addUploadHandler(UploadHandler handler) {
		return addHandler(handler, UploadEvent.getType());
	}

	@Override
	public Widget getUploadInputWidget() {
		return uploadForm;
	}
}
